package org.example.guice;

import org.example.asm.ClassReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.Collectors;

public class ClassPathClassNameScanner {

    private static final Logger logger = LoggerFactory.getLogger(ClassPathClassNameScanner.class);

    public Set<String> doScan(String basePackage) {
        Set<URL> urls = doFindClassPathUrl(basePackage.replace(".", "/"));
        return urls.stream().flatMap(url -> retrieveMatchingFiles(url)
                .stream()).map(this::doRetrieveBeanClassName).collect(Collectors.toSet());
    }

    protected Set<URL> doFindClassPathUrl(String path) {
        Set<URL> result = new LinkedHashSet<>(16);
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        try {
            Enumeration<URL> resourceUrls = cl.getResources(path);
            while (resourceUrls.hasMoreElements()) {
                result.add(resourceUrls.nextElement());
            }
        } catch (IOException e) {
            logger.error("ClassLoader reader system resources error!", e);
        }
        return result;
    }

    protected Set<File> retrieveMatchingFiles(URL url) {
        File rootDir = new File(url.getFile()).getAbsoluteFile();
        if (!rootDir.exists()) {
            // Silently skip non-existing directories.
            return Collections.emptySet();
        }
        if (!rootDir.isDirectory()) {
            // Complain louder if it exists but is no directory.
            return Collections.emptySet();
        }
        if (!rootDir.canRead()) {
            return Collections.emptySet();
        }
        Set<File> result = new LinkedHashSet<>(8);
        doRetrieveMatchingFiles(rootDir, result);
        return result;
    }

    protected void doRetrieveMatchingFiles(File dir, Set<File> result) {
        for (File content : listDirectory(dir)) {
            // FIXME 路径匹配判断
            if (content.isDirectory()) {
                if (content.canRead()) {
                    doRetrieveMatchingFiles(content, result);
                }
            } else {
                result.add(content);
            }
        }
    }

    protected String doRetrieveBeanClassName(File file) {
        SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor();
        try {
            InputStream is = Files.newInputStream(file.toPath());
            ClassReader reader = new ClassReader(is);
            reader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
        } catch (IOException e) {
            logger.error("error reading file!");
        }
        return visitor.getClassName();
    }


    protected File[] listDirectory(File dir) {
        File[] files = dir.listFiles();
        if (files == null) {
            if (logger.isInfoEnabled()) {
                logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
            }
            return new File[0];
        }
        Arrays.sort(files, Comparator.comparing(File::getName));
        return files;
    }
}
